//多温度测量和输出+PID控制+LCD显示
//引脚2——控温的PTC加热体的开。5——STEP步进引脚。6——DIR步进电机旋转方向引脚。8——enable步进电机始能引脚。
//7——控制风扇的关闭。3--控制风扇的风力大小。4——reset引脚，可通过程序对该引脚输入低电平——重置单片机，如d040e。
//9——控制A4988驱动的12v开关
//引脚数据的输入，需要以e或E结尾表示输入一段字符结束
//SPI通信引脚：10，11，12，13   IIC引脚：A4、A5分别为SDA、SCL。
//PT100接10/11/12/13。A4 A5用于接LCD显示器。
//功能，需要将7号引脚=HIGH才会使用PID控温；
#include <PID_v1.h>
#include <Adafruit_MAX31865.h>  //pt100测温
//---------------------LCD显示
#include <LiquidCrystal_I2C.h>  //#I2C线LCD显示库文件
#include <Wire.h>     //#I2C线通信
LiquidCrystal_I2C lcd(0x27, 30, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
//int l2_x=0;
//int dir=1;
//---------------------//
//---------------max31865--PT100-----
// Use software SPI: CS, DI, DO, CLK
Adafruit_MAX31865 max = Adafruit_MAX31865(53,51,50,52);//(10, 11, 12, 13);
// The value of the Rref resistor. Use 430.0 for PT100 and 4300.0 for PT1000
#define RREF      430.0
// 100.0 for PT100, 1000.0 for PT1000
#define RNOMINAL  100.0
float tempTPT100; //保存PT100测量的温度值
//----------------------------
//#define PIN_INPUT 0
const int PIN_Output1 = 2; //输出控制的引脚
//#define dirPin 2   //步进电机方向引脚、步进角度引脚、使能引脚

const int pinEnable = 8;

//###########################
double Setpoint, Input, Output;
double Kp = 50, Ki = 100, Kd = 150;
//double Kp=1, Ki=10, Kd=3;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
//########################//
int A0data = 0; //保存红外检测到的数据
//**************************************
int A1data = 0; //保存红外检测到的数据
int justFirstNum = 0; //表示当步进电机初始化时，遇到红外传感器不停止，而其它不是第一次碰到则停止。
//**************************************
String tempStrData;//保存上位机发送过来的数据
void setup(void)
{
  //引脚2——控温的PTC加热体的开闭。3——控制风扇风力大小。5——STEP步进引脚。6——DIR步进电机旋转方向引脚。
  //7——控制风扇的关闭。8——enable步进电机始能引脚。10——DS18B20。
  pinMode(11, OUTPUT);
  pinMode(pinEnable, OUTPUT);
  pinMode(6, OUTPUT); //用于输出引脚
  pinMode(7, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(5, OUTPUT);

  digitalWrite(4, HIGH); //必须在pinMODE前面，重置时才不会出错
  pinMode(4, OUTPUT);
  pinMode(9, OUTPUT);

  digitalWrite(7, LOW); //关闭风扇的控制开关引脚，防止一开始风扇就微转
  //####################################
  pinMode(PIN_Output1, OUTPUT);
  pinMode(13, OUTPUT); //控制led灯亮灭
  //Input = analogRead(PIN_INPUT);//初始化时的赋值，实际上没有用处
  Input = 26; //室温，即初始化PID输入的初始值
  Setpoint = 27;    //设置PID的目标值，即室温27℃
  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  //########################################//
  //---------------------LCD显示
  lcd.init();                      // initialize the lcd
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Target  Current");
  //lcd.setCursor(0,1);
  //lcd.print("waiting a secend");
  //lcd.autoscroll();//自动滚动
  //delay(2000);
  delay(500);
  //---------------------//
  // start serial port
  Serial.begin(9600);
  //-----------PT100-------
  max.begin(MAX31865_2WIRE);  // set to 2WIRE or 4WIRE as necessary
  //-----------PT100-------
  //+++++++++++++++++++++++++红外检测模块++++++++++
  //初始化步进电机的原点
  int initStep = 0;

  digitalWrite(9, HIGH); //利用三极管，接通步进电机驱动模块上的步进电机电源线
  digitalWrite(pinEnable, LOW); //开启始能引脚

//  while (A0data <= 500) //判断红外是否检测到物体，检测到则会跳出while循环
//  {
//    A0data = analogRead(A0);
//    Serial.println(A0data);
//    dirOutput_stepNum_delayTimeMS(6, 1, 5, 2, 50); //俯视步进电机，逆时针旋转
//    if (A0data > 500)
//    {
//      dirOutput_stepNum_delayTimeMS(6, 0, 5, 26, 20);
//      //dirOutput_stepNum_delayTimeMS(6,0,5,35,20);//50代表旋转90°，即，1.8*50
//      //dirOutput_stepNum_delayTimeMS(6,1,5,45,20);//50代表旋转90°，即，1.8*50
//    }
//    initStep += 2;
//    //    if(initStep<=180)
//    //    {
//    //      while(1) //当步进电机旋转超过180还没有找到零点时，限制在这个循环中
//    //      {
//    //        Serial.println("this is an error , initializing the stepmotor's position");
//    //        delay(2000);
//    //      }
//    //    }
//    //    delay(1000);
//  }
  //+++++++++++++++++++++++++红外检测模块++++++++++
}
//====================步进电机
//该函数的延迟时间单位为，微妙
//声明函数，对步进电机的操作。各参数的含义，方向引脚，旋转方向，步进引脚，步进步数，每步的延迟时间
void dirOutput_stepNum_delayTimeUS(int dirPin, int dirOutput, int stepPin, int stepNum, int delayTime)
{
  pinMode(stepPin, OUTPUT); //因为方向引脚和步进引脚可能没有预先设置
  pinMode(dirPin, OUTPUT);
  //当dirPin为0时，顺时针转动。为1时，逆时针转动。
  if (dirOutput == 1) //通过第四位0或1控制对应引脚的高低电平
  {
    digitalWrite(dirPin, 1);
  }
  else if (dirOutput == 0)
  {
    digitalWrite(dirPin, 0);
  }
  //digitalWrite(dirPin,dirOutput);//设置方向引脚的电平，和上面有区别。输入的数据1或0，可能无法代替HIGH或LOW
  // 电机慢速旋转
  for (int x = 0; x < stepNum; x++) //默认的全步数是转一圈200步
  { //执行一次for循环是一个脉冲
    //**************************************
    if (justFirstNum != 0 && ((A0data = analogRead(A0)) > 300 || (A1data = analogRead(A1)) > 300))
    { //设置当旋转步进电机的A0和A1红外碰到物体时还在旋转的话，就停止旋转并反向转动，直到红外没有碰到物体
      Serial.print("A0data + A1data= ");
      Serial.print(A0data);
      Serial.print(" , ");
      Serial.println(A1data);
      Infrared_TimeMS(dirPin, dirOutput, stepPin, stepNum, delayTime);
      break;
    }
    //A1data=0;
    if (Serial.available() > 0) //当有输入时，就停止旋转
      return 0;
    //**************************************

    Serial.println(stepNum - x); //每转一步就输出一次
    digitalWrite(stepPin, HIGH);
    //delay(delayTime);
    delayMicroseconds(delayTime);
    digitalWrite(stepPin, LOW);
    //delay(delayTime);
    delayMicroseconds(delayTime);
  }
  //  Serial.println("execute dirOutput_stepNum_delayTime");
}
//该函数的延迟时间单位为，毫秒，
//声明函数，对步进电机的操作。各参数的含义，方向引脚，旋转方向，步进引脚，步进步数，每步的延迟时间
void dirOutput_stepNum_delayTimeMS(int dirPin, int dirOutput, int stepPin, int stepNum, int delayTime)
{
  pinMode(stepPin, OUTPUT); //因为方向引脚和步进引脚可能没有预先设置
  pinMode(dirPin, OUTPUT);
  //当dirPin为0时，顺时针转动。为1时，逆时针转动。
  if (dirOutput == 1) //通过第四位0或1控制对应引脚的高低电平
  {
    digitalWrite(dirPin, 1);
  }
  else if (dirOutput == 0)
  {
    digitalWrite(dirPin, 0);
  }
  //digitalWrite(dirPin,dirOutput);//设置方向引脚的电平，和上面有区别。输入的数据1或0，可能无法代替HIGH或LOW
  // 电机慢速旋转
  for (int x = 0; x < stepNum; x++) //默认的全步数是转一圈200步
  { //执行一次for循环是一个脉冲
    //**************************************
    if (justFirstNum != 0 && ((A0data = analogRead(A0)) > 300 || (A1data = analogRead(A1)) > 300))
    {
      Serial.print("A0data + A1data= ");
      Serial.print(A0data);
      Serial.print(" , ");
      Serial.println(A1data);
      Infrared_TimeMS(dirPin, dirOutput, stepPin, stepNum, delayTime);
      break;
    }
    //A1data=0;
    if (Serial.available() > 0) //当有输入时，就停止旋转
      return 0;
    //**************************************

    Serial.println(stepNum - x); //每转一步就输出一次
    Serial.println(dirOutput);
    digitalWrite(stepPin, HIGH);
    delay(delayTime);
    //delayMicroseconds(delayTime);
    digitalWrite(stepPin, LOW);
    delay(delayTime);
    //delayMicroseconds(delayTime);

  }
  //  Serial.println("execute dirOutput_stepNum_delayTime");
}

//步进电机碰到红外传感器，反转
void Infrared_TimeMS(int dirPin, int dirOutput, int stepPin, int stepNum, int delayTime)
{
  //当dirPin为0时，顺时针转动。为1时，逆时针转动。
  if (dirOutput == 1) //该函数用于碰到红外后反转，故与上面函数方向引脚相反
  {
    digitalWrite(dirPin, 0);
  }
  else if (dirOutput == 0)
  {
    digitalWrite(dirPin, 1);
  }
  // 电机慢速旋转
  for (int x = 0; x < stepNum; x++) //默认的全步数是转一圈200步
  { //执行一次for循环是一个脉冲
    //**************************************
    if (justFirstNum != 0 && ((A0data = analogRead(A0)) < 300 && (A1data = analogRead(A1)) < 300))
    {
      Serial.print("go away infrared sensor");
      Serial.print("A0data and A1data= ");
      Serial.print(A0data);
      Serial.print(" , ");
      Serial.println(A1data);
      break;
    }
    if (Serial.available() > 0) //当有输入时，就停止旋转
      return 0;//这会不会是一个bug？因为当红外检测到碰撞时，如果输入数据，步进电机的红外还是处于碰撞状态
    //**************************************

    Serial.println(stepNum - x); //每转一步就输出一次
    Serial.println(dirOutput);
    digitalWrite(stepPin, HIGH);
    delay(delayTime);
    //delayMicroseconds(delayTime);
    digitalWrite(stepPin, LOW);
    delay(delayTime);
    //delayMicroseconds(delayTime);
  }
}
//====================//

//PT100和max31864温度控制错误检验的函数
void Pt100EroorFun(uint8_t fault)
{
  Serial.print("PT100 have Fault 0x"); Serial.println(fault, HEX);
  if (fault & MAX31865_FAULT_HIGHTHRESH) {
    Serial.println("RTD High Threshold");
  }
  if (fault & MAX31865_FAULT_LOWTHRESH) {
    Serial.println("RTD Low Threshold");
  }
  if (fault & MAX31865_FAULT_REFINLOW) {
    Serial.println("REFIN- > 0.85 x Bias");
  }
  if (fault & MAX31865_FAULT_REFINHIGH) {
    Serial.println("REFIN- < 0.85 x Bias - FORCE- open");
  }
  if (fault & MAX31865_FAULT_RTDINLOW) {
    Serial.println("RTDIN- < 0.85 x Bias - FORCE- open");
  }
  if (fault & MAX31865_FAULT_OVUV) {
    Serial.println("Under/Over voltage");
  }
}
void loop(void)
{
  if (justFirstNum == 0)//说明执行了一次start函数，就可以执行电机旋转中的红外检测函数了
    justFirstNum = 1;
  //-----------PT100-------
  uint16_t rtd = max.readRTD();
  //  Serial.print("RTD value: "); Serial.println(rtd);
  float ratio = rtd;
  ratio /= 32768;
  //  Serial.print("Ratio = ");
  //  Serial.println(ratio,8);
  //  Serial.print("Resistance = ");
  //  Serial.println(RREF*ratio,8);
  //  Serial.print("Temperature = ");
  tempTPT100 = max.temperature(RNOMINAL, RREF); //保存PT100测量的温度
  //输出温度值
  Serial.print("T"); //输出PT100测量的温度值
  Serial.println(tempTPT100);

  // Check and print any faults
  uint8_t fault = max.readFault();
  if (fault) {
    Pt100EroorFun(fault);
    max.clearFault();
  }
  //  Serial.println();
  delay(100); //测温延时
  //-----------PT100-------
  //---------------------LCD显示
  lcd.setCursor(0, 1);
  lcd.print("A");
  lcd.setCursor(1, 1);
  lcd.print(Setpoint);//显示为目标温度
  lcd.setCursor(8, 1);
  lcd.print("B");
  lcd.setCursor(9, 1);
  lcd.print(tempTPT100); //PT100的测量温度
  //---------------------//

  //#############################
  //Input = analogRead(PIN_INPUT);
  Input = tempTPT100;
  myPID.Compute();
  char a;
  //********************上位机信息读取*************
  while (Serial.available() > 0)
  {
    a = char(Serial.read());
    //以e或E作为每行数据的结束符。也可以不以“e/E”为结束符，
    //但是程序控制连续发送串口数据需要以e/E为结束符，否则数据出现丢失现象
    if (a == 'e' || a == 'E')
    {
      break;//e结尾不算入个数中——————注意-----输入的数据不能用e、E作为非结尾数据
    }
    else
      tempStrData += a;
    if (tempStrData.length() == 30) //若连续输入的字符大于30，则重置为空
      tempStrData = "";
    //tempStrData+=char(Serial.read());  //将上位机输入的bytes数据转为char，再组成字符串，也是数组
    delay(2);//必不可少的的延迟，不然数据读取不完全
  }
  if (tempStrData == "1")
  {
    digitalWrite(13, 1); //若上位机输入1，则点亮13引脚的led灯
    tempStrData = "";
  }
  else if (tempStrData == "0")
  {
    digitalWrite(13, 0);
    tempStrData = "";
  }
  else if (tempStrData == "R" || tempStrData == "r") //输入reset串口数据，则重置单片机
  {
    Serial.println("Reset MCU");
    Serial.println(tempStrData);
    tempStrData = "";
    digitalWrite(4, LOW);
    delay(1000);
  }
  else if (tempStrData == "C" || tempStrData == "c") //输入reset串口数据，则重置单片机
  {
    Serial.println("Reset IO");
    Serial.println(tempStrData);
    tempStrData = "";
    for (int i = 2; i <= 13; i++) //用于将所有引脚置0
    {
      digitalWrite(i, LOW);
      delay(2);
    }
  }
  else if (tempStrData.length() == 4)
  {
    //四位字符串，限制输入格式的内容为，第一位d或D，第二和三位为引脚号，第四位为高低电平
    if (tempStrData[0] == 'd' || tempStrData[0] == 'D')
    {
      int pinInt = (tempStrData[1] - 48) * 10 + (tempStrData[2] - 48); //字符串对应的数组就是int类型
      //这里注意，判断数字的大小使用的时int数值类型，不是字符类型
      if ((tempStrData[3] - 48) == 1) //通过第四位0或1控制对应引脚的高低电平
      {
        digitalWrite(pinInt, HIGH);
        delay(200);
        Serial.print("this pin ");
        Serial.print(pinInt);
        Serial.println(" is HIGH");
      }
      else if ((tempStrData[3] - 48) == 0)
      {
        digitalWrite(pinInt, LOW);
        delay(200);
        Serial.print("this pin ");
        Serial.print(pinInt);
        Serial.println(" is LOW");
      }
      //      Serial.println();
    }
    //四位字符串，表示输入的目标温度，格式为：第一位t或T，第二/三/四位为目标温度
    if (tempStrData[0] == 't' || tempStrData[0] == 'T')
    { //这里只针对了一个目标温度，======
      int T = (tempStrData[1] - 48) * 100 + (tempStrData[2] - 48) * 10 + (tempStrData[3] - 48); //字符串对应的数组就是int类型
      //这里注意，判断数字的大小使用的时int数值类型，不是字符类型

      Setpoint = T; //设置目标温度1
      Serial.println("the goal temperature is");
      Serial.println(Setpoint);
    }
    Serial.println(tempStrData);
    tempStrData = "";
  }
  else if (tempStrData.length() == 6)
  {
    //六位字符串，限制输入格式的内容为，第一位p或P，第二和三位为引脚号，第四五六位为数值输入量范围0~255
    if (tempStrData[0] == 'p' || tempStrData[0] == 'P')
    {
      int pinInt = (tempStrData[1] - 48) * 10 + (tempStrData[2] - 48); //字符串对应的数组就是int类型
      pinMode(pinInt, OUTPUT); //设置引脚的输出模式
      int analogInt = (tempStrData[3] - 48) * 100 + (tempStrData[4] - 48) * 10 + (tempStrData[5] - 48);
      //constrain(x,a,b)将x设置在a和b之间，小于a返回a，大于b返回b。
      analogWrite(pinInt, constrain(analogInt, 0, 255)); //将第四五六位的数字量写入单片机对应的引脚
      Serial.println("this pin are _ing PWM");
    }
    Serial.println(tempStrData);
    tempStrData = "";
  }
  else if (tempStrData.length() == 15) //即，为步进电机的输入15位字符时
  {
    Serial.println(tempStrData);
    //第1位为步骤识别符号b或B，第2~3位为方向引脚的引脚号，第4位为方向引脚的高低电平，第5~6位为步进引脚的引脚号，
    //第7~9位为旋转步数，第10~13位为延迟时间，默认最好大于10delay函数，第14~15位为始能引脚号
    if (tempStrData[0] == 'm' || tempStrData[0] == 'M') //执行毫秒的数据格式
    {
      Serial.println(tempStrData);
      int dirPinPin = (tempStrData[1] - 48) * 10 + (tempStrData[2] - 48); //方向引脚的引脚号
      int dirPinHL = (tempStrData[3] - 48); //方向引脚的高低电平
      int stepPinPin = (tempStrData[4] - 48) * 10 + (tempStrData[5] - 48); //步进引脚的引脚号
      int stepPinNum = ((tempStrData[6] - 48) * 100 + (tempStrData[7] - 48) * 10 + (tempStrData[8] - 48)); //stepNum旋转步数
      int stepWaitTime = ((tempStrData[9] - 48) * 1000 + (tempStrData[10] - 48) * 100 + (tempStrData[11] - 48) * 10 + (tempStrData[12] - 48)); //每步的延时
      int enablePin = (tempStrData[13] - 48) * 10 + (tempStrData[14] - 48); //A4988步进电机驱动模块的始能引脚Enable
      //数组元素输出的是该字符对应的ascii码值。所以，用的时候针对数字，可以减去48，即0字符对应的ascii码。即可得到对应于数字的值。
      //pinMode(enablePin,OUTPUT);//设置始能引脚的输出模式
      //digitalWrite(enablePin,LOW); //打开使能引脚
      //可以考虑限制旋转速度，当然也可以在PC端进行限制
      if (stepWaitTime >= 10)
        dirOutput_stepNum_delayTimeMS(dirPinPin, dirPinHL, stepPinPin, stepPinNum, stepWaitTime); //调用步进电机函数
      else
        dirOutput_stepNum_delayTimeMS(dirPinPin, dirPinHL, stepPinPin, stepPinNum, 10); //限制转动速度，每步最小delay时间为10ms
      //dirOutput_stepNum_delayTime(dirPinPin,dirPinHL,stepPinPin,stepPinNum,stepWaitTime);
      //digitalWrite(enablePin,HIGH); //关闭步进电机驱动的enable引脚
    }
    else if (tempStrData[0] == 'u' || tempStrData[0] == 'U') //执行，US微秒的数据格式
    {
      Serial.println(tempStrData);
      int dirPinPin = (tempStrData[1] - 48) * 10 + (tempStrData[2] - 48); //方向引脚的引脚号
      int dirPinHL = (tempStrData[3] - 48); //方向引脚的高低电平
      int stepPinPin = (tempStrData[4] - 48) * 10 + (tempStrData[5] - 48); //步进引脚的引脚号
      int stepPinNum = ((tempStrData[6] - 48) * 100 + (tempStrData[7] - 48) * 10 + (tempStrData[8] - 48)); //stepNum旋转步数
      int stepWaitTime = ((tempStrData[9] - 48) * 1000 + (tempStrData[10] - 48) * 100 + (tempStrData[11] - 48) * 10 + (tempStrData[12] - 48)); //每步的延时
      int enablePin = (tempStrData[13] - 48) * 10 + (tempStrData[14] - 48); //A4988步进电机驱动模块的始能引脚Enable
      //数组元素输出的是该字符对应的ascii码值。所以，用的时候针对数字，可以减去48，即0字符对应的ascii码。即可得到对应于数字的值。
      //pinMode(enablePin,OUTPUT);//设置始能引脚的输出模式
      //digitalWrite(enablePin,LOW); //打开使能引脚
      //可以考虑限制旋转速度，当然也可以在PC端进行限制
      if (stepWaitTime >= 500)
        dirOutput_stepNum_delayTimeUS(dirPinPin, dirPinHL, stepPinPin, stepPinNum, stepWaitTime); //调用步进电机函数
      else
        dirOutput_stepNum_delayTimeUS(dirPinPin, dirPinHL, stepPinPin, stepPinNum, 500); //限制转动速度，每步最小delay时间为500us，否则会出现问题
      //dirOutput_stepNum_delayTime(dirPinPin,dirPinHL,stepPinPin,stepPinNum,stepWaitTime);
      //digitalWrite(enablePin,HIGH); //关闭步进电机驱动的enable引脚
    }
    Serial.println(tempStrData);
    tempStrData = "";
  }
  else if (tempStrData.length() > 0)
  {
    Serial.println(tempStrData);//该语句只是为了检测输入什么，就返回什么而已
    tempStrData = "";
  }
  //  Serial.print("PID Output： ");
  //  Serial.println(Output);
  //**********************************************
  //控制电路的通断，进而控制温度
  //analogWrite(PIN_Output1,Output);
  //int pin7=digitalRead(7);//获得该引脚的电平
  //if(pin7==HIGH) //判断该引脚的电平
  //{
  if (Output <= 45) //Ourput是pid算法输出的值
    //  if(tempTPT100>=Setpoint)   //传感器1，控制3号引脚
  {
    ////Serial.print("超过该温度了");  #超过了设定温度则，断开电路，即继电器
    ////Serial.print(settemp);
    digitalWrite(PIN_Output1, LOW);
    delay(100);
  }
  else {
    ////Serial.print("低于该温度");
    ////Serial.print(settemp);
    ////Serial.print("    ");
    digitalWrite(PIN_Output1, HIGH);
    delay(100);
  }
  //}

  //#############################
}
